/*
 * ULSF Sagittarius IDA 
 * Copyright (C) 2012, John Pritchard, all rights reserved.
 * 
 * This program is free software: you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see
 * <http://www.gnu.org/licenses/>.
 */
package sagittarius.airfoil;

import sagittarius.Visualization;
import static sagittarius.airfoil.Geometry.*;

import fv3.font.GlyphVector;
import fv3.math.Matrix;
import fv3.math.VertexArray;

import com.badlogic.gdx.graphics.Mesh;
import com.badlogic.gdx.graphics.VertexAttribute;

/**
 * A kind of airfoil geometry
 * 
 * @see ModelVertices
 * @see ChordIterator
 */
public final class AirfoilVertices
    extends MathAbstract
    implements Cloneable,
               java.lang.Iterable<ChordVertex>
{


    public final Geometry geometry;
    /**
     * Number of vertices in vertex list (N[V])
     */
    public final int np;
    /**
     * Number of interior edge vertex (index + 1; 0 &lt; n &lt;= N[V])
     */
    public final int ne;
    /**
     * Length of vertex array (N[A])
     */
    public final int vp;
    /**
     * Index of vertex for interior edge (index; 0 &lt;= v &lt; N[A])
     */
    public final int ve;
    /**
     * An abstract vertex set is transformed into a practical section
     * vertex set.  More specifically, the model is in NACA-490 order
     * (X- forward) and the section is in the opposite orientation for
     * X+ forward (see {@link sagittarius.design.Model}).
     */
    private float[] model;

    private float x, y, chord, section[];

    private boolean initialized;

    private Mesh mesh;

    private String description;

    private GlyphVector description3;


    public AirfoilVertices(Geometry geometry, int count){
        super();
        this.geometry = geometry;

        final int n = ((count/geometry.dimension.number)*geometry.dimension.number);//(even)

        this.np = n+1;
        this.ne = (n/geometry.dimension.number);
        this.vp = (this.np*geometry.dimension.number);
        this.ve = (this.ne*geometry.dimension.number)-geometry.dimension.number;
        this.model = new float[this.vp];
    }
    public AirfoilVertices(AirfoilVertices from, Geometry to){
        this(to,(from.np-1));

        VertexArray vary = new VertexArray(this.np);
        {
            /*
             * Copy In
             */
            final float[] frArray = from.getModelVertices();
            final float[] varray = vary.array();

            final int frDom = from.geometry.domain();
            final int frRan = from.geometry.range();

            final int toDom = to.domain();
            final int toRan = to.range();

            final int frCC = from.geometry.dimension.number;
            final int toCC = to.dimension.number;


            for (int c0 = 0, c1 = 0; c1 < this.vp; c0 += frCC, c1 += toCC){

                varray[c1+toDom] = frArray[c0+frDom];
                varray[c1+toRan] = frArray[c0+frRan];
            }
        }
        {
            /*
             * Each geometric conversion may be defined by a rotation,
             * and the dimensional copy-out that follows.
             */
            final Matrix conversion = new Matrix();
            switch(from.geometry){

            case TB2:
            case TB3:

                switch(to){

                case TB2:
                case TB3:
                    break;

                case LT2:
                case LT3:

                    conversion.rotateZ(PI);

                    break;

                default:
                    throw new IllegalArgumentException(String.format("Undefined conversion from '%s' to '%s'",from.geometry.name(),to.name()));
                }
                break;


            case LT2:
            case LT3:
                switch(to){

                case TB2:
                case TB3:

                    conversion.rotateZ(PI);

                    break;

                case LT2:
                case LT3:
                    break;

                default:
                    throw new IllegalArgumentException(String.format("Undefined conversion from '%s' to '%s'",from.geometry.name(),to.name()));
                }
                break;

            default:
                throw new IllegalArgumentException(String.format("Undefined conversion from '%s' to '%s'",from.geometry.name(),to.name()));
            }

            vary.transform(conversion);
        }

        if (Geometry.VertexDimension.XYZ == this.geometry.dimension){

            vary.copy(this.model);
        }
        else {
            /*
             * Copy Out
             */
            final float[] frArray = vary.array();
            final float[] toArray = this.model;

            final int frDom = from.geometry.domain();
            final int frRan = from.geometry.range();

            final int toDom = to.domain();
            final int toRan = to.range();

            final int frCC = 3;
            final int toCC = to.dimension.number;


            for (int c0 = 0, c1 = 0; c1 < this.vp; c0 += frCC, c1 += toCC){

                toArray[c1+toDom] = frArray[c0+frDom];
                toArray[c1+toRan] = frArray[c0+frRan];
            }
        }
    }



    public int index2(int index){
        if (index < this.ve){
            final int distance = (this.ve - index);
            return (this.ve + distance);
        }
        else
            throw new IllegalArgumentException(String.valueOf(index));
    }
    public String getDescription(){
        return this.description;
    }
    public AirfoilVertices setDescription(String d){
        this.description = d;
        return this;
    }
    public GlyphVector getDescription3(){
        GlyphVector description3 = this.description3;
        if (null == description3){
            description3 = Font.toString(this.toString());
            this.description3 = description3;
        }
        return description3;
    }
    public String toString(){
        return this.description;
    }
    public boolean isInitialized(){
        return this.initialized;
    }
    public boolean isTransformed(){
        return (this.initialized && (null != this.section) && (0.0f != this.y));
    }
    public final float getTransformedX(){
        return this.x;
    }
    public final float getTransformedY(){
        return this.y;
    }
    public final float getTransformedChord(){
        return this.chord;
    }
    public final float getModelVerticesArea(){
        final float[] vertices = this.getModelVertices();
        final int count = (vertices.length>>1);

        float A = 0.0f;
        for (int cc = 0; cc < count; cc++){
            int xx = (cc*2);
            int zz = (xx+1);
            float x0 = vertices[xx];
            float z0 = vertices[zz];
            xx += 2;
            zz += 2;
            float x1 = vertices[xx];
            float z1 = vertices[zz];

            A += (x0*z1 - x1*z0);
        }
        return (A / 2.0f);
    }
    public float[] getModelVertices(){
        return this.model;
    }
    public float[] copyModelVertices(){
        return this.model.clone();
    }
    public AirfoilVertices setModelVertices(float[] model){
        if (model.length == vp){
            this.initialized = true;
            this.model = model;
            return this;
        }
        else
            throw new IllegalArgumentException(String.format("%d/%d",model.length,this.vp));
    }
    public AirfoilVertices copyModelVertices(float[] model){
        if (model.length == vp){
            this.model = model.clone();
            return this;
        }
        else
            throw new IllegalArgumentException(String.format("%d/%d",model.length,this.vp));
    }
    public java.util.Iterator<ChordVertex> iterator(){

        return new ChordIterator(this,this.model);
    }
    public float[] getSectionVertices(){
        return this.section;
    }
    public float[] copySectionVertices(){
        return this.section.clone();
    }
    /**
     * Before getting vertices, translate the objective (lateral foil)
     * section location in the Y dimension.
     *
     * @param y Scaled Y coordinate for this section
     */
    public final void modelTranslate(float y){

        this.y = y;
    }
    /**
     * Transform a model to the objective (lateral foil) section
     * location and dimension.
     *
     * Because the model chord is 1.0, the scaled chord is also the
     * scale factor.
     * 
     * @param oex Scaled X coordinate of origin edge
     * @param chord Scaled chord of this section
     *
     * @see #setModelVertices(float[])
     * @see #copyModelVertices(float[])
     * @see #translate(float)
     */
    public final void modelTransform(float oex, float chord){
        this.x = oex;
        this.chord = chord;

        final float[] vertices = this.model;
        switch(this.geometry.dimension.number){

        case 2:
            {
                for (int xx = 0, zz = 1; xx < this.vp; xx += 2, zz += 2){

                    vertices[xx] = (vertices[xx] * chord) + oex;
                    vertices[zz] *= chord;
                }
            }
            break;
        case 3:
            {
                for (int xx = 0, yy = 1, zz = 2; xx < this.vp; xx += 3, yy += 3, zz += 3){

                    vertices[xx] = (vertices[xx] * chord) + oex;
                    vertices[yy] *= chord;
                    vertices[zz] *= chord;
                }
            }
            break;
        default:
            throw new Error(this.geometry.dimension.name());
        }

        this.section = vertices;
    }
    /**
     * Intersection
     * 
     * @param x Coordinate in X
     *
     * @return One or two vec3-coordinates: (Edge) or (Bottom,Top).
     * Otherwise null for not found (out of range).
     * 
     * @see #init(int)
     * @see #transform(float,float)
     * @see #translate(float)
     */
    public final float[] getSectionVertices3At(float x){

        ChordIterator it = new ChordIterator(this,this.section);
        for (ChordVertex cv: it){

            switch (cv.in(x)){
            case Current:
                {
                    it.destroy();
                    return cv.getVertices();
                }
            case Domain:
                if (it.hasNext()){
                    it.destroy();
                    return cv.getVerticesFor(x);
                }
                else {
                    it.destroy();
                    return cv.getVerticesFor(x);
                }

            case None:
                break;
            }
        }
        it.destroy();
        return null;
    }
    public AirfoilVertices clone(){
        try {
            AirfoilVertices clone = (AirfoilVertices)super.clone();
            clone.model = clone.model.clone();
            if (null != clone.section)
                clone.section = clone.section.clone();
            return clone;
        }
        catch (CloneNotSupportedException exc){
            throw new InternalError();
        }
    }
    public Mesh getMesh(){
        Mesh mesh = this.mesh;
        if (null == mesh && null != this.model){

            final VertexArray array = this.getDescription3();

            final float s = (1.0f/400.0f);

            array.scale(s,s,s);

            array.translate(0.4f,-0.2f,0.0f);

            switch(this.geometry.dimension.number){

            case 2:
                array.addLinesXY(this.model);
                break;
            case 3:
                array.addLinesXYZ(this.model);
                break;
            default:
                throw new Error(this.geometry.dimension.name());
            }

            array.translateY(0.5f);

            final float[] string = array.array();

            final int stringCount = (string.length/3);

            mesh = new Mesh(Visualization.Static,stringCount,0,
                            VertexAttribute.Position());

            mesh.setVertices(string);

            this.mesh = mesh;
        }
        return mesh;
    }
    public void dispose(){
        Mesh mesh = this.mesh;
        if (null != mesh){
            this.mesh = null;

            mesh.dispose();
        }
    }
    public void list(){

        System.out.println("#");
        System.out.println("# Airfoil data listing");

        if (null != this.description){
            System.out.printf( "# %s%n",this.description);
        }
        System.out.println("#");
        System.out.println("# [vertex_index] (a_position)");
        System.out.println("#");

        final float[] vertices = this.model;

        switch(this.geometry.dimension.number){

        case 2:
            {
                final int verticesCount = vertices.length/2;
                float[] a_position = new float[2];

                for (int vv = 0, ofs; vv < verticesCount; vv++){
                    ofs = (vv*2);
                    System.arraycopy(vertices,ofs,a_position,0,2);

                    System.out.printf("[%d] (%2.2g,%2.2g)%n",vv,a_position[0],a_position[1]);
                }
            }
            break;
        case 3:
            {
                final int verticesCount = vertices.length/3;
                float[] a_position = new float[3];

                for (int vv = 0, ofs; vv < verticesCount; vv++){
                    ofs = (vv*3);
                    System.arraycopy(vertices,ofs,a_position,0,3);

                    System.out.printf("[%d] (%2.2g,%2.2g,%2.2g)%n",vv,a_position[0],a_position[1],a_position[2]);
                }
            }
            break;
        default:
            throw new Error(this.geometry.dimension.name());
        }
    }


    /**
     * Iteration over sparse list skips null elements in {@link
     * #hasNext()}.
     */
    public static class Iterator 
        extends Object
        implements java.util.Iterator<AirfoilVertices>
    {

        private final int length;
        private final AirfoilVertices[] list;
        private int index;

        public Iterator(AirfoilVertices[] list){
            super();
            if (null == list){
                this.list = null;
                this.length = 0;
            }
            else {
                this.list = list.clone();
                this.length = this.list.length;
            }
        }


        public boolean hasNext(){

            while (this.index < this.length && null == this.list[this.index]){

                this.index += 1;
            }
            return (this.index < this.length);
        }
        public AirfoilVertices next(){

            if (this.index < this.length){

                return this.list[this.index++];
            }
            else
                throw new java.util.NoSuchElementException();
        }
        public void remove(){
            throw new UnsupportedOperationException();
        }
    }
}
